package com.zero.manage.web.utils.generate;

import cn.hutool.core.util.StrUtil;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.zero.manage.web.components.dto.GenDto;
import com.zero.manage.web.components.entity.TableEntity;
import io.gitee.zerowsh.enums.ToolEnums;
import com.zero.manage.web.utils.CommonUtils;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.io.IOUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static java.util.regex.Pattern.compile;

/**
 * 代码生成器
 */
@Log4j2
public class GenUtils {
    /**
     * 基类忽略的字段
     */
    private static List<String> BASE_DATA_ENTITY_LIST = Arrays.asList("id", "create_time", "update_time", "remarks");
    private static List<String> BASE_TREE_ENTITY_LIST = new ArrayList<String>() {{
        addAll(BASE_DATA_ENTITY_LIST);
        addAll(Arrays.asList("name", "parent_id", "parent_ids"));
    }};

    /**
     * 数据库类型转java类型
     */
    private static Map<String, String> DATABASE_TURN_JAVA_MAP = new HashMap<String, String>() {{
        put("tinyint", "Integer");
        put("int", "Integer");
        put("bigint", "Long");
        put("decimal", "BigDecimal");
        put("bit", "Integer");
        put("char", "String");
        put("varchar", "String");
        put("text", "String");
        put("date", "Date");
    }};

    /**
     * 需要生成的模板
     *
     * @return list<String>
     */
    private static List<String> getTemplates() {
        List<String> templates = new ArrayList<>();
        templates.add("template/Entity.java.vm");
        templates.add("template/Query.java.vm");
        templates.add("template/Dao.java.vm");
        templates.add("template/Service.java.vm");
        templates.add("template/ServiceImpl.java.vm");
        templates.add("template/Controller.java.vm");
        return templates;
    }

    /**
     * 封装模板中使用的数据
     */
    private static VelocityContext setVelocityContext(TableEntity tableEntity) {
        //封装模板中使用的数据
        Map<String, Object> map = new HashMap<>(11);
        map.put("tableName", tableEntity.getTableName());
        map.put("comments", tableEntity.getComments());
        map.put("className", tableEntity.getClassName());
        map.put("classname", tableEntity.getClassname());
        map.put("columns", tableEntity.getColumns());
        map.put("existTimestamp", tableEntity.isExistTimestamp());
        map.put("existBigDecimal", tableEntity.isExistBigDecimal());
        map.put("existDate", tableEntity.isExistDate());
        map.put("baseEntity", tableEntity.getBaseEntity());
        map.put("packagePath", tableEntity.getPackagePath());
        map.put("author", tableEntity.getAuthor());
        return new VelocityContext(map);
    }

    public static void generatorCode(DynamicRoutingDataSource ds, GenDto dto) {
        generatorCode(ds, dto, null);
    }

    /**
     * 代码生成
     *
     * @param dto 参数
     * @return 返回生成的文件路径集合
     */
    public static void generatorCode(DynamicRoutingDataSource ds, GenDto dto, ZipOutputStream zip) {
        String catalog = dto.getCatalog();
        DatabaseDao databaseDao = DatabaseDao.handleDataSource(ds, dto.getDatabaseInfoId());
        List<GenDto.TableAndComment> tableInfo = dto.getTableInfo();
        for (GenDto.TableAndComment tableAndRemarks : tableInfo) {
            // 列信息
            List<ColumnInfo> columnInfoList = databaseDao.getColumns(catalog, tableAndRemarks.getTableName());
            TableEntity tableEntity = handleTableInfo(tableAndRemarks, dto);
            handleColumnInfo(tableEntity, columnInfoList, dto);

            //设置velocity资源加载器
            Properties prop = new Properties();
            prop.put("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
            Velocity.init(prop);
            VelocityContext context = setVelocityContext(tableEntity);
            //获取模板列表
            List<String> templates = getTemplates();
            ToolEnums.WhetherEnums download = dto.getDownload();
            String packagePath = dto.getPackagePath();
            // 将.处理成/
            if (StrUtil.isNotBlank(packagePath)) {
                packagePath = packagePath.replace(".", "\\");
            }
            if (Objects.equals(download, ToolEnums.WhetherEnums.T)) {
                for (String template : templates) {
                    //渲染模板
                    StringWriter sw = new StringWriter();
                    Template tpl = Velocity.getTemplate(template, "UTF-8");
                    tpl.merge(context, sw);
                    try {
                        String filePath = getCoverFileName(template, tableEntity.getClassName(), packagePath);
                        //添加到zip
                        zip.putNextEntry(new ZipEntry(filePath));
                        IOUtils.write(sw.toString(), zip, "UTF-8");
                        IOUtils.closeQuietly(sw);
                        zip.flush();
                        zip.closeEntry();
                        log.info(filePath);
                    } catch (IOException e) {
                        throw new RuntimeException("模板生成失败，表名=" + tableEntity.getTableName(), e);
                    }
                }
            } else {
                for (String template : templates) {
                    String filePath = getCoverFileName(template, tableEntity.getClassName(), dto.getBasicPath() + packagePath);
                    Template tpl = Velocity.getTemplate(template, "UTF-8");
                    File file = new File(filePath);
                    if (!file.getParentFile().exists()) {
                        file.getParentFile().mkdirs();
                    }
                    if (!file.exists()) {
                        try {
                            file.createNewFile();
                        } catch (IOException e) {
                            throw new RuntimeException("创建文件失败", e);
                        }
                    }
                    try (FileOutputStream outStream = new FileOutputStream(file);
                         OutputStreamWriter writer = new OutputStreamWriter(outStream, StandardCharsets.UTF_8);
                         BufferedWriter sw = new BufferedWriter(writer)) {
                        tpl.merge(context, sw);
                        sw.flush();
                        log.info(filePath);
                    } catch (IOException e) {
                        throw new RuntimeException("模板生成失败，表名=" + tableEntity.getTableName(), e);
                    }
                }
            }
        }
    }

    /**
     * 处理表信息
     *
     * @param tableAndRemarks
     * @param dto
     */
    private static TableEntity handleTableInfo(GenDto.TableAndComment tableAndRemarks, GenDto dto) {
        //表信息
        TableEntity tableEntity = new TableEntity();
        tableEntity.setBaseEntity(dto.getBaseEntity());
        tableEntity.setPackagePath(dto.getPackagePath());
        tableEntity.setAuthor(dto.getAuthor());
        tableEntity.setTableName(tableAndRemarks.getTableName());
        tableEntity.setComments(CommonUtils.handleObjTurnStr(tableAndRemarks.getTableComment()));
        //表名转换成Java类名
        String className = tableToJava(tableEntity.getTableName(), dto.getTablePrefix());

        tableEntity.setClassName(className.substring(0, 1).toUpperCase() + className.substring(1));
        tableEntity.setClassname(className);
        return tableEntity;
    }

    private static void handleColumnInfo(TableEntity tableEntity, List<ColumnInfo> columnInfoList, GenDto dto) {
        //列信息
        List<TableEntity.ColumnEntity> columnList = new ArrayList<>();
        for (ColumnInfo item : columnInfoList) {
            // 不同基类不同处理
            if (Objects.equals(ToolEnums.BaseEntityEnums.BaseDataEntity, dto.getBaseEntity())) {
                if (BASE_DATA_ENTITY_LIST.contains(item.getColumnName())) {
                    continue;
                }
            } else if (Objects.equals(ToolEnums.BaseEntityEnums.BaseTreeEntity, dto.getBaseEntity())) {
                if (BASE_TREE_ENTITY_LIST.contains(item.getColumnName())) {
                    continue;
                }
            }
            //列名转换成Java属性名
            //列的数据类型，转换成Java类型
            String attrType = DATABASE_TURN_JAVA_MAP.get(item.getTypeName().toLowerCase());
            if (!tableEntity.isExistTimestamp()) {
                tableEntity.setExistTimestamp(Objects.equals(attrType, "Timestamp"));
            }
            if (!tableEntity.isExistBigDecimal()) {
                tableEntity.setExistBigDecimal(Objects.equals(attrType, "BigDecimal"));
            }
            if (!tableEntity.isExistDate()) {
                tableEntity.setExistDate(Objects.equals(attrType, "Date"));
            }
            TableEntity.ColumnEntity columnEntity = TableEntity.ColumnEntity.builder()
                    .columnName(item.getColumnName())
                    .dataType(item.getTypeName())
                    .comments(item.getRemarks())
                    .attrType(attrType)
                    .isPrimaryKey(item.isPrimaryKey())
                    .attrName(lineToHump(item.getColumnName()))
                    .build();
            columnList.add(columnEntity);
        }
        tableEntity.setColumns(columnList);
    }

    /**
     * 下划线转驼峰
     */
    private static String lineToHump(String str) {
        if (str.contains("_")) {
            Pattern linePattern = compile("_(\\w)");
            str = str.toLowerCase();
            Matcher matcher = linePattern.matcher(str);
            StringBuffer sb = new StringBuffer();
            while (matcher.find()) {
                matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
            }
            matcher.appendTail(sb);
            return sb.toString();
        } else {
            return str;
        }
    }

    /**
     * 表名转换成Java类名
     */
    private static String tableToJava(String tableName, String tablePrefixArray) {
        if (StrUtil.isNotBlank(tablePrefixArray)) {
            for (String tablePrefix : tablePrefixArray.split(",")) {
                if (tableName.indexOf(tablePrefix) == 0) {
                    tableName = StrUtil.subAfter(tableName, tablePrefix, false);
                }
            }
        }
        return lineToHump(tableName);
    }

    private static final String ENTITY = "Entity.java";
    private static final String QUERY = "Query.java";
    private static final String DAO = "Dao.java";
    private static final String SERVICE = "Service.java";
    private static final String SERVICE_IMPL = "ServiceImpl.java";
    private static final String CONTROLLER = "Controller.java";
    private static final String COVER_FILE_EXPRESSION = "%s\\components\\%s\\%s";

    /**
     * 生成文件(有就覆盖)
     *
     * @param template  模板
     * @param className 文件名
     * @param path      生成包路径 d:/zero/com/zero
     * @return 生成文件路径
     */
    private static String getCoverFileName(String template, String className, String path) {
        if (template.contains(ENTITY)) {
            return String.format(COVER_FILE_EXPRESSION, path, "entity", className + ENTITY);
        } else if (template.contains(QUERY)) {
            return String.format(COVER_FILE_EXPRESSION, path, "query", className + QUERY);
        } else if (template.contains(DAO)) {
            return String.format(COVER_FILE_EXPRESSION, path, "dao", className + DAO);
        } else if (template.contains(SERVICE)) {
            return String.format(COVER_FILE_EXPRESSION, path, "service", className + SERVICE);
        } else if (template.contains(SERVICE_IMPL)) {
            return String.format(COVER_FILE_EXPRESSION, path, "service/impl", className + SERVICE_IMPL);
        } else {
            return String.format(COVER_FILE_EXPRESSION, path, "controller", className + CONTROLLER);
        }
    }
}
